#!/usr/bin/python
"""The main module of Defence."""
from __future__ import division

import pygame
pygame.init()

import os.path
from itertools import cycle
from random import shuffle

import gui
from game import Game, AutoGame, MenuGame
from menu import Menu

from settings import settings
import events
from graphics import graphics

class Music(object):
	"""An abstraction of pygame's music. Given an iterable of tracks this cycles
	them randomly"""
	
	def __init__(self, tracks=()):
		self._paused = False
		
		pygame.mixer.music.set_endevent(events.SKIP)
		
		self.tracks = list(tracks)
		shuffle(self.tracks)
		
		self.iter = cycle(self.tracks)
	
	def play(self, track, loops=0):
		"""Loop a specific track, independant of existing tracks, loops amount
		of times or indefinitely"""
		pygame.mixer.music.load(track)
		pygame.mixer.music.play(loops)
		
		if self.paused:
			pygame.mixer.music.pause()
	
	def _getpaused(self):
		"""Return paused state"""
		return self._paused
	def _setpaused(self, pause):
		"""Set paused state, pause/unpause music to match"""
		if pause and not self.paused:
			pygame.mixer.music.pause()
		elif self.paused:
			pygame.mixer.music.unpause()
	paused = property(_getpaused, _setpaused, doc="""Pause the current game and
		music playback""")
	
	def next(self):
		"""Skip to the next track"""
		self.play(self.iter.next())
	
	def event(self, event):
		"""Handle events"""
		if event.type == events.skip:
			self.next()

class Defence(object):
	"""The main application. Runs game and menu, handles events."""
	music = Music()
	hotkeys = {
			settings.controls.debug: [events.DEBUG],
			settings.controls.pause: [events.PAUSE],
			settings.controls.menu: [events.MENU],
			settings.controls.screenshot: [events.SCREENSHOT],
			settings.controls.skip: [events.SKIP]}
	
	def __init__(self, surface=None, game=None, debug=False):
		if surface is None:
			surface = pygame.surface.Surface((640, 480))
		
		self.surface = surface
		self.game = game
		self.showmenu = True
		
		self._debug = debug
		self._menu = Menu()
		self._clock = pygame.time.Clock()
		self._quit = False
	
	def run(self):
		"""Run the main loop of Defence"""
		self.music.next()
		
		while not self._quit:
			if settings.game.sleep >= 0:
				pygame.time.wait(settings.game.sleep)
			
			self._clock.tick(50)
			
			for event in pygame.event.get():
				self.event(event)
			else:
				if self._quit:
					break
			
			self.update()
			
			self.surface.fill((0, 0, 0))
			self.draw(self.surface)
			pygame.display.flip()
	
	def update(self):
		"""Update the game, menu, etc"""
		if self.game is not None:
			self.game.update()
	
	def draw(self, surface):
		"""Draw the game, menu and possibly debugging things to the surface"""
		#insert menu crap
		if self.game is not None:
			self.game.draw(surface, debug=self.debug)
		
		if self.showmenu:
			self._menu.draw(surface)
		
		x, y = pygame.mouse.get_pos()
		
		pygame.draw.line(surface, (255, 0, 0), (x-16, y), (x+16, y))
		pygame.draw.line(surface, (255, 0, 0), (x, y-16), (x, y+16))
		
		if self.debug:
			def text(text):
				"""Render text in a common style. No keyword arguments available
				to convert to using partial"""
				return settings.fonts.text.render(text, False, (255, 255, 255),
					(0, 0, 0))
			
			if self.game is not None:
				frame = self.game.frame
			else:
				frame = "N/A"
			
			surface.blit(text("FRAME: %s" % (frame)), (0, 0))
			surface.blit(text("FPS:   %.2f" % (self._clock.get_fps())), (0, 10))
	
	def event(self, event):
		"""Handle events, passing unhandled events to the menu, if active, or
		else to the game"""
		if event.type == pygame.QUIT:
			self._quit = True
		elif event.type == events.DEBUG:
			self.debug = not self.debug
		elif event.type == events.SCREENSHOT:
			surface = self.surface.copy()
			entry = gui.TextEntry("Screenshot name", font=settings.fonts.text, 
				content=".png", cursor=0)
			
			try:
				filename = entry.input(self.surface)
			except gui.InputCancelled:
				pass
			else:
				self.screenshot(filename, surface=surface)
		elif event.type == events.MENU:
			if not isinstance(self.game, MenuGame):
				self.showmenu = not self.showmenu
			
			self._menu.current = [self._menu.menus["default"]]
		elif event.type == events.SKIP:
			self.music.next()
		elif event.type == events.LEVELCHANGE:
			self.change_level(event.campaign, event.level)
		elif event.type == pygame.KEYDOWN and event.key in self.hotkeys:
			for event in self.hotkeys[event.key]:
				self.event(pygame.event.Event(event))
		else:
			if self.showmenu:
				self._menu.event(event)
			elif self.game is not None:
				self.game.event(event)
	
	def change_level(self, campaign, level, auto=False, menu=False):
		"""Start a new game on level. If auto the game cannot be paused. If menu
		the menu cannot be closed"""
		if menu:
			self.game = MenuGame(campaign, level)
		elif auto:
			self.game = AutoGame(campaign, level)
		else:
			self.game = Game(campaign, level)
		
		self.music = Music(self.game.tracks)
		self.music.next()
	
	def _getdebug(self):
		"""Return debug state""" 
		return self._debug
	def _setdebug(self, value):
		"""Set debug state and mirror it in game"""
		self._debug = value	
		if self.game is not None:
			self.game.debug = value
	debug = property(_getdebug, _setdebug, doc="""To enable debugging
		information such as FPS visible""")
	
	def screenshot(self, filename, surface=None):
		"""Save the surface to a file."""
		if surface is None:
			surface = self.surface
		
		filepath = os.path.join(settings.paths.screenshots, filename)
		pygame.image.save(surface, filepath)

def main():
	"""Initialise a pygame display, load graphics and start a Defence instance
	"""
	pygame.mouse.set_visible(False)
	pygame.display.set_mode((640, 480)) 
	pygame.display.set_caption("Defence") 
	screen = pygame.display.get_surface()

	graphics.load()
		
	defence = Defence(surface=screen)
	defence.change_level(Menu.campaign, "main", menu=True)
	
	defence.run()

if __name__ == "__main__":
	if settings.psyco:
		try:
			import psyco
		except ImportError:
			pass
		else:
			psyco.full()
	
	if settings.profile:
		import cProfile
		cProfile.run("main()")
	else:
		main()
